package com.xinchuang.socket;

/**
 * TODO
 *
 * @author zkwczx
 * @version 1.0
 * @date 2023/11/21 14:47
 */

import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xinchuang.service.image.TaskRunService;
import com.xinchuang.utils.OfficeUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * WebSocket的操作类
 *
 * @author zkwczx
 */
@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {

    /**
     * 静态变量，用来记录当前在线连接数，线程安全的类。
     */
    private static AtomicInteger onlineSessionClientCount = new AtomicInteger(0);

    /**
     * 存放所有在线的客户端
     */
    private static Map<String, Session> onlineSessionClientMap = new ConcurrentHashMap<>();

    /**
     * 存放所有在线的ID
     */
    private static Map<String, String> onlineUserId = new ConcurrentHashMap<>();

    /**
     * 连接sid和连接会话
     */
    private String userId;

    private Session session;

    private static CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();

    private static Map<String, Session> sessionPool = new HashMap<String, Session>();

    private Map<String, Lock> sessionLocks = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法。由前端<code>new WebSocket</code>触发
     *
     * @param session 与某个客户端的连接会话，需要通过它来给客户端发送消息
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        /**
         * session.getId()：当前session会话会自动生成一个id，从0开始累加的。
         */
        log.info("连接建立中 ==> session_id = {}， userId = {}", session.getId(), userId);
        //加入 Map中。将页面的sid和session绑定或者session.getId()与session
            //在线数加1
        onlineSessionClientMap.put(session.getId(), session);
        onlineUserId.put(userId, session.getId());
        onlineSessionClientCount.incrementAndGet();
        this.userId = userId;
        this.session = session;
        sendToOneBySessionId(session.getId(), toJson("code", "CREATE_USER", "message", userId));
        log.info("连接建立成功，当前在线数为：{} ==> 开始监听新连接：session_id = {}， userId = {}", onlineSessionClientCount, session.getId(), userId);
    }

    private String toJson(String code, String codeMsg, String message, String messageMsg) {
        JSONObject json = new JSONObject();
        json.put(code, codeMsg);
        json.put(message, messageMsg);
        return json.toJSONString();
    }

    /**
     * 连接关闭调用的方法。由前端<code>socket.close()</code>触发
     *
     * @param session
     */
    @OnClose
    public void onClose(Session session) {
        log.info("关闭websocket：userId = {}. sessionId = {}", userId, session.getId());
        // 从 Map中移除
        onlineSessionClientMap.remove(session.getId());
        onlineUserId.remove(userId);
        //在线数减1
        onlineSessionClientCount.decrementAndGet();
    }

    /**
     * 收到客户端消息后调用的方法。由前端<code>socket.send</code>触发
     * * 当服务端执行toSession.getAsyncRemote().sendText(xxx)后，前端的socket.onmessage得到监听。
     *
     * @param message
     */
    @OnMessage
    public void onMessage(String message) {
        /**
         * html界面传递来得数据格式，可以自定义.
         * {"sid":"user-1","message":"hello websocket"}
         */
        log.info("获取的信息:" + message + ",userId=" + userId);
        JSONObject jsonObject = JSON.parseObject(message);
        String code = jsonObject.getString("code");
        TaskRunService service = null;
        switch (code) {
            case "AUTO_CORRECT":
                service = SpringUtil.getBean(TaskRunService.class);
                service.createAutoCorrectTask(
                        jsonObject.getString("tarId"),
                        jsonObject.getString("jzimgid"),
                        jsonObject.getString("newimgid"),
                        userId
                );
                break;
            case "AUTO_SHIBIE_END":
                try {
                    service = SpringUtil.getBean(TaskRunService.class);
                    service.createAutoRecognitionto(
                            jsonObject.getString("imgId"),
                            jsonObject.getJSONArray("positions"),
                            userId
                    );
                } catch (Exception e) {
                    log.error("识别异常：{}", e.getMessage(), e);
                    sendToOneBySessionId(session.getId(), toJson("code", "AUTO_RECOGNITION_ERROR", "message", "影像识别异常，请重试！"));
                }
                break;
            case "AUTO_SUBJECT":
                try {
                    if (StringUtils.isBlank(jsonObject.getString("subjectName"))) {
                        sendToOneBySessionId(session.getId(), toJson("code", "AUTO_SUBJECT_ERROR", "message", "请输入专题的名称"));
                        break;
                    }
                    if (StringUtils.isBlank(jsonObject.getString("fileUrl"))) {
                        sendToOneBySessionId(session.getId(), toJson("code", "AUTO_SUBJECT_ERROR", "message", "请选择一个影像"));
                        break;
                    }
                    service = SpringUtil.getBean(TaskRunService.class);
                    service.createSubject(
                            userId,
                            jsonObject.getString("subjectName"),
                            jsonObject.getString("format"),
                            jsonObject.getString("fileUrl")
                    );
                } catch (Exception e) {
                    log.error("专题异常：{}", e.getMessage(), e);
                    sendToOneBySessionId(session.getId(), toJson("code", "AUTO_SUBJECT_ERROR", "message", "专题创建异常，请重试！"));
                }
                break;
            default:
                sendToOneBySessionId(session.getId(), toJson("code", "AUTO_CORRECT", "message", "STARTING"));
                break;
        }
        log.info("服务端收到客户端消息 ==> userId = {}, code = {}, message = {}", userId, code, message);
    }

    /**
     * 发生错误调用的方法
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket发生错误，错误信息为：" + error.getMessage(), error);
        if (session != null && session.isOpen()) {
            Lock sessionLock = getSessionLock(session.getId());
            sessionLock.lock();
            try {
                JSONObject json = new JSONObject();
                json.put("code", "ERROR");
                json.put("message", "websocket异常，问题为：" + error.getMessage());
                session.getAsyncRemote().sendText(json.toJSONString());
            } catch (Exception e) {
                log.error("发送消息时出现异常: " + e.getMessage());
            } finally {
                sessionLock.unlock();
            }
        } else {
            log.error("会话不存在或已关闭，无法发送消息");
        }
    }

    /**
     * 群发消息
     */
    @Scheduled(fixedRate = 1000 * 10)
    private void sendToAll() {
        // 遍历在线map集合
        onlineSessionClientMap.forEach((sessionId, toSession) -> {
            sendToOneBySessionId(sessionId, toJson("code", "TEST", "message", "SUCCESS"));
        });
    }

    /**
     * 指定发送消息
     *
     * @param userId
     * @param message
     */
    public void sendToOneByUserId(String userId, String message) {
        try {
            Session session = onlineSessionClientMap.get(onlineUserId.get(userId));
            if (session != null && session.isOpen()) {
                Lock sessionLock = getSessionLock(session.getId());
                sessionLock.lock();
                try {
                    session.getAsyncRemote().sendText(message);
                    log.info("服务端给客户端发送消息 ==> userId = {}, message = {}", userId, message);
                } catch (Exception e) {
                    log.error("发送消息时出现异常: " + e.getMessage());
                } finally {
                    sessionLock.unlock();
                }
            } else {
                log.error("会话不存在或已关闭，无法发送消息");
            }
        } catch (Exception e) {
            log.error("发送消息时出现异常: " + e.getMessage());
        }
    }

    /**
     * 指定发送消息
     *
     * @param sessionId
     * @param message
     */
    public void sendToOneBySessionId(String sessionId, String message) {
        try {
            Session session = onlineSessionClientMap.get(sessionId);
            if (session != null && session.isOpen()) {
                Lock sessionLock = getSessionLock(session.getId());
                sessionLock.lock();
                try {
                    session.getAsyncRemote().sendText(message);
                    log.info("服务端给客户端发送消息 ==> sessionId = {}, message = {}", sessionId, message);
                } catch (Exception e) {
                    log.error("发送消息时出现异常: " + e.getMessage());
                } finally {
                    sessionLock.unlock();
                }
            } else {
                log.error("会话不存在或已关闭，无法发送消息");
            }
        } catch (Exception e) {
            log.error("发送消息时出现异常: " + e.getMessage());
        }
    }

    private Lock getSessionLock(String sessionId) {
        sessionLocks.putIfAbsent(sessionId, new ReentrantLock());
        return sessionLocks.get(sessionId);
    }

}

